
Imports std

Public MustInherit Class procContextBase

    Public Const errprefix As String = "stderr, "

    Public Class def
        Public Const idletime As Double = 500
    End Class

    Public Class defenvname
        Public Const cmdprefix As String = "cmd_prefix"
        Public Const processPriority As String = "process_priority"
        Public Const inheritenv As String = "inherit_env"
        Public Const username As String = "username"
        Public Const password As String = "password"
        Public Const domain As String = "domain"
    End Class

    Public Class defenvvalue
        Public Const cmdprefix As String = emptyString
        Public Const processPriority As String = "normal"
        Public Const inheritenv As Boolean = True
        Public Const username As String = emptyString
        Public Const password As String = emptyString
        Public Const domain As String = emptyString
    End Class

    Protected Friend _proc As Process
    Public idletime As Double
    Protected Friend WithEvents _iohandle As procIOHandleBase
    Protected Friend WithEvents _envhandle As procEnvHandle
    Protected Friend _errout As String
    Protected Friend _errfinish As Boolean
    Protected Friend _envs As map(Of String, String)

    Public MustOverride Function getallenvs(ByRef envs As map(Of String, String)) As Boolean
    Public MustOverride Function getenv(ByVal name As String, ByRef value As String) As Boolean
    Public MustOverride Function setenv(ByVal name As String, ByVal value As String) As Boolean
    Public MustOverride Function setallenvs(ByVal envs As map(Of String, String)) As Boolean
    Public MustOverride Function output(ByVal out As String) As Boolean
    Protected MustOverride Function depackSpecialKey(ByVal input As String, _
                                                        ByRef e As Windows.Forms.KeyEventArgs) As Boolean
    Protected MustOverride Function isTerminateKey(ByVal e As Windows.Forms.KeyEventArgs) As Boolean
    Protected MustOverride Sub initialIOHandle()
    Protected MustOverride Sub finishoutput()

    Public Function errout() As String
        Return _errout
    End Function

    Public Function errfinish() As Boolean
        Return _errfinish
    End Function

    Public Function proc() As Process
        Return _proc
    End Function

    Public Function isProcAlive() As Boolean
        On Error Resume Next
        If Not _proc Is Nothing AndAlso _proc.HasExited Then
            _proc = Nothing
        End If
        Return Not _proc Is Nothing
    End Function

    Protected Friend Function createProcess(ByVal cmd As String, ByVal args As vector(Of String)) As Boolean
        If args Is Nothing Then
            Dim a(-1) As String
            Return createProcess(cmd, a)
        Else
            Return createProcess(cmd, args.data)
        End If
    End Function

    Protected Friend Function createProcess(ByVal cmd As String, ByVal args() As String) As Boolean
        On Error Resume Next
        assert(Not isProcAlive(), "_proc is running.")
        _iohandle.stop()
        _envhandle.stop()
        _errout = Nothing
        _errfinish = False
        _proc = New Process()
        If Not args Is Nothing AndAlso args.Length > 0 Then
            _proc.StartInfo.Arguments = strcombine(args, character.blank)
        End If
        _iohandle.process = _proc
        '_envhandle.iohandle = _iohandle
        _envhandle.start()
        If getallenvs(_envs) Then
            Dim inherenv As Boolean = defenvvalue.inheritenv
            inherenv = Convert.ToBoolean(_envs(defenvname.inheritenv))
            procEnvHandle.setenvs(_proc, _envs, Not inherenv)

            Dim path As String = _envs(procEnvHandle.extraEnvName.workingpath)
            If strcont(cmd, filesystem.directorySeperator) AndAlso Not isemptystring(path) Then
                cmd = combinepath(path, cmd)
            End If

            Dim cmdprefix As String = _envs(defenvname.cmdprefix)
            If Not isemptystring(cmdprefix) Then
                _proc.StartInfo.FileName = cmdprefix + character.blank + cmd
            Else
                _proc.StartInfo.FileName = cmd
            End If

#If Not (PocketPC Or Smartphone) Then
            Dim username As String = _envs(defenvname.username)
            If Not isemptystring(username) Then
                _proc.StartInfo.UserName = username
                Dim password As Security.SecureString = Nothing
                password = New Security.SecureString()
                Dim passwordStr As String = _envs(defenvname.password)
                Dim i As Int32
                For i = 0 To strlen(passwordStr) - 1
                    password.AppendChar(passwordStr(i))
                Next
                _proc.StartInfo.Password = password
                Dim domain As String = _envs(defenvname.domain)
                _proc.StartInfo.Domain = domain
            End If
#End If
        End If

        If Not _proc.Start() Then
            Dim msg As String = Nothing
            If Not args Is Nothing OrElse args.Length = 0 Then
                Dim i As Int32
                For i = 0 To args.Length() - 1
                    msg += args(i) + character.blank
                Next
            Else
                msg = "[none]"
            End If
            If Not output("failed to start process, cmd = " + cmd + ", args() = " + msg) Then
                raiseError("output returns false.", errorHandle.errorType.exclamation)
            End If
            _proc = Nothing
            Return False
        Else
#If Not (PocketPC OrElse SmartPhone) Then
            Dim pri As String = _envs(defenvname.processPriority)
            _proc.PriorityClass = processPriority(pri)
#End If
            _iohandle.start()
            Return True
        End If
    End Function

    Public Function handleInput(ByVal s As String) As Boolean
        Dim key As Windows.Forms.KeyEventArgs = Nothing
        If depackSpecialKey(s, key) Then
            If isTerminateKey(key) Then
                If isProcAlive() Then
                    _iohandle_outputReceive("kill process by user input [ctrl+Q]" + newline.incode, False)
                    closeProcess(_proc)
                Else
                    finishoutput()
                End If
            Else
                finishoutput()
            End If

            Return True
        Else
            If isProcAlive() Then
                Return input(s)
            Else
                Dim words As vector(Of String) = Nothing
                Dim args As vector(Of String) = Nothing
                kickNewlineChar(s)
                strsepar(s, character.blank, words, character.quoteMark, True, False)
                If words.size() > 0 Then
                    Dim i As Int32
                    args = New vector(Of String)
                    For i = 1 To words.size() - 1
                        args.push_back(words(i))
                    Next
                    kickLeft(words(0), character.quoteMark)
                    kickRight(words(0), character.quoteMark)
                    If Not createProcess(words(0), args) Then
                        finishoutput()
                        Return False
                    Else
                        Return True
                    End If
                Else
                    Return False
                End If
            End If
        End If
    End Function

    Public Function input(ByVal s As String) As Boolean
        assert(isProcAlive(), "_proc exited or not start yet.")
        _iohandle.input(s)

        Return True
    End Function

    Public Overridable Sub initial()
        _proc = Nothing
        _envhandle = New procEnvHandle
        idletime = def.idletime
        _envs = New map(Of String, String)
        initialIOHandle()
    End Sub

    Public Sub New()
        initial()
    End Sub

    Private Sub _iohandle_errorReceive(ByVal s As String, ByVal exited As Boolean) Handles _iohandle.errorReceive
        If Not isemptystring(s) Then
            _envhandle.envoutputReceive(s, exited)
            _errout += s
        End If
        If exited Then
            If Not isemptystring(_errout) Then
                _errout = errprefix + _errout
                output(_errout)
            End If
            _errfinish = True
        End If
    End Sub

    Private Sub _iohandle_outputReceive(ByVal s As String, ByVal exited As Boolean) Handles _iohandle.outputReceive
        If Not isemptystring(s) Then
            output(s)
        End If
        If exited Then
            While Not _errfinish
                Windows.Forms.Application.DoEvents()
                sleep(idletime)
            End While
            finishoutput()
        End If
    End Sub

    Private Sub _envhandle_environmentChanged(ByVal env As pair(Of String, String)) _
                                                Handles _envhandle.environmentChanged
        setenv(env.first, env.second)
    End Sub

    Private Sub _envhandle_environmentRequired(ByRef envs As map(Of String, String)) _
                                                Handles _envhandle.environmentRequired
        getallenvs(envs)
    End Sub
End Class
